package com.demo.workSpace;

import com.demo.animation.DesktopEffect;
import com.demo.animation.Rotary;
import com.demo.animation.Square;

import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

public class WorkSpace extends ViewGroup {
  private static final int INVALID_SCREEN = -1;
  // The velocity at which a fling gesture will cause us to snap to the next screen
  private static final int SNAP_VELOCITY = 1000;
  private final static boolean  TOUCH_INTERCEPT = true;
  
  // the default screen index
  private int mDefaultScreen;
  // The current screen index
  private int mCurrentScreen;
  // The next screen index
  private int mNextScreen = INVALID_SCREEN;
  private Paint mPaint;

  // The scroller which scroll each view
  private Scroller mScroller;
  // A tracker which to calculate the velocity of a mouvement
  private VelocityTracker mVelocityTracker;

  // Tha last known values of X and Y
  private float mLastMotionX;
  private float mLastMotionY;

  // The current touch state
  private boolean mTouchState = TOUCH_INTERCEPT;
  // The minimal distance of a touch slop
  private int mTouchSlop;

  //when change the screen callback it
  private ScrollToScreenCallback mScrollToScreenCallback;
  private int mCurrentScrollX ;
  private Camera mCamera;
  private Matrix mMatrix;
  
  private int mScrollingBounce = 80;
  /**
   * Used to inflate the Workspace from XML.
   *
   * @param context The application's context.
   * @param attrs The attribtues set containing the Workspace's customization values.
   */
  public WorkSpace(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
    this.setBackgroundColor(Color.WHITE);
  }

  /**
   * Used to inflate the Workspace from XML.
   *
   * @param context The application's context.
   * @param attrs The attribtues set containing the Workspace's customization values.
   * @param defStyle Unused.
   */
  public WorkSpace(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    mDefaultScreen = 0;
//    this.setBackgroundColor(Color.WHITE);
    initWorkspace();
  }

  /**
   * Initializes various states for this workspace.
   */
  private void initWorkspace() {
    mScroller = new Scroller(getContext());
    mCurrentScreen = mDefaultScreen;

    mPaint = new Paint();
    mPaint.setDither(false);
    //see the setTouchSlop()
    mTouchSlop = ViewConfiguration.getTouchSlop();
    DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
    mScrollingBounce = displayMetrics.widthPixels / 2;
    Log.v("mScrollingBounce", String.valueOf(mScrollingBounce));
//    mScrollingBounce = 
  }


  boolean isDefaultScreenShowing() {
    return mCurrentScreen == mDefaultScreen;
  }

  public int getCurrentScreen() {
    return mCurrentScreen;
  }
  
  public void setCurrentScreen(int mCurrentScreen) {
	//make it between 0 to ChildCount;
    mCurrentScreen = Math.max(0, Math.min(mCurrentScreen, getChildCount()));
    scrollTo(mCurrentScreen * getWidth(), 0);
    invalidate();
  }

  /**
   * Shows the default screen (defined by the firstScreen attribute in XML.)
   */
  void showDefaultScreen() {
    setCurrentScreen(mDefaultScreen);
  }

  @Override
  public void computeScroll() {
	//动画结束,使屏幕移动到指定位置
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      postInvalidate();
    }
    else if (mNextScreen != INVALID_SCREEN) {
      mCurrentScreen = mNextScreen;
      mNextScreen = INVALID_SCREEN;
    }
  }

  /**
   * ViewGroup.dispatchDraw() supports many features we don't need: clip to padding, layout animation, animation
   * listener, disappearing children, etc. The following implementation attempts to fast-track the drawing dispatch by
   * drawing only what we know needs to be drawn.
   */
  @Override
  protected void dispatchDraw(Canvas canvas) {
    // Determine if we need to draw every child or only the current screen 
    // equal 
    // (mTouchState != TOUCH_STATE_SCROLLING) && (mNextScreen == INVALID_SCREEN);
//    boolean fastDraw = mTouchState != TOUCH_INTERCEPT && mNextScreen == INVALID_SCREEN;
//    // If we are not scrolling or flinging, draw only the current screen
//    if (fastDraw) {
////      View v = getChildAt(mCurrentScreen);
////      drawChild(canvas, v, getDrawingTime());
//    	 drawScreen(canvas, mCurrentScreen, getDrawingTime()); 
//    }
//    else {
      final long drawingTime = getDrawingTime();
//      // If we are flinging, draw only the current screen and the target screen
//      if (mNextScreen >= 0 
//    	  && mNextScreen < getChildCount() 
//    	  && Math.abs(mCurrentScreen - mNextScreen) == 1) {
//    	  drawScreen(canvas, mCurrentScreen, drawingTime); 
////        drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
//    	  drawScreen(canvas, mNextScreen, drawingTime); 
////        drawChild(canvas, getChildAt(mNextScreen), drawingTime);
//      }
////      else {
////        // If we are scrolling, draw all of our children
        final int count = getChildCount();
        DesktopEffect effect = new Rotary();
        for (int i = 0; i < count; i++) {
        	canvas.save();
        	effect.drawScreen(this, canvas, i);
        	drawChild(canvas, getChildAt(i), drawingTime);
//        	drawScreen(canvas, i, drawingTime); 
        	canvas.restore();
//          drawChild(canvas, getChildAt(i), drawingTime);
        }
//      }
//    }
  }
  
  protected void drawScreen(Canvas canvas, int screen, long drawingTime) { 
      final int width = getWidth(); 
      final int scrollWidth = screen * width; 
      final int scrollX = this.getScrollX();  
      if(scrollWidth > scrollX + width || scrollWidth + width < scrollX) { 
          return; 
      } 
      final View child = getChildAt(screen); 
      final int faceIndex = screen; 
//      final float faceDegree = currentDegree - faceIndex * preFaceDegree; 
      
      final float faceDegree = (scrollX -scrollWidth) * 90 / width;
      if(faceDegree > 90 || faceDegree < -90) { 
          return; 
      } 
      final float centerX = 
    	  (scrollWidth < scrollX)? scrollWidth + width :scrollWidth ; 
      final float centerY = getHeight()/2;
      final Camera camera = new Camera(); 
      final Matrix matrix = new Matrix();
//      canvas.save(); 
      camera.save(); 
      camera.rotateY(-faceDegree); 
      camera.getMatrix(matrix); 
      camera.restore(); 
      matrix.preTranslate(-centerX, -centerY); 
      matrix.postTranslate(centerX, centerY); 
//      matrix.preTranslate(scrollX -scrollWidth, 0);
//      matrix.preTranslate(100, 0);
      canvas.concat(matrix); 
      drawChild(canvas, child, drawingTime); 
//      child.setBackgroundColor(Color.BLACK); 
//      canvas.restore(); 
  } 

  /**
   * Measure the workspace AND also children
   */
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    final int width = MeasureSpec.getSize(widthMeasureSpec);
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    if (widthMode != MeasureSpec.EXACTLY) {
      throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
    }

    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY) {
      throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
    }

    // The children are given the same width and height as the workspace
    final int count = getChildCount();
    for (int i = 0; i < count; i++) {
      getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
    }

  }

  /**
   * Overrided method to layout child
   */
  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    int childLeft = 0;
    final int count = getChildCount();
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != View.GONE) {
        final int childWidth = child.getMeasuredWidth();
        child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
        childLeft += childWidth;
      }
    }
//    setPadding(0, 200, 100, 0);
  }

  @Override
  public boolean dispatchUnhandledMove(View focused, int direction) {
    if (direction == View.FOCUS_LEFT) {
      if (getCurrentScreen() > 0) {
        scrollToScreen(getCurrentScreen() - 1);
        return true;
      }
    }
    else if (direction == View.FOCUS_RIGHT) {
      if (getCurrentScreen() < getChildCount() - 1) {
        scrollToScreen(getCurrentScreen() + 1);
        return true;
      }
    }
    return super.dispatchUnhandledMove(focused, direction);
  }

  /**
   * This method JUST determines whether we want to intercept the motion. If we return true, onTouchEvent will be called
   * and we do the actual scrolling there.
   */
  //it handle when down    
  //return true intercept, 
  //return false not intercept
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {

    /*
     * Shortcut the most recurring case: the user is in the dragging state and he is moving his finger. We want to
     * intercept this motion.
     */
    final int action = ev.getAction();
    if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_INTERCEPT)) {
      return true;
    }

    final float x = ev.getX();
    final float y = ev.getY();

    switch (action) {
    	case MotionEvent.ACTION_DOWN:
	        // Remember location of down touch
	        mLastMotionX = x;
	        mLastMotionY = y;
	        /*
	         * If being flinged and user touches the screen, initiate drag; otherwise don't. mScroller.isFinished should be
	         * false when being flinged.
	         */
	        mTouchState = mScroller.isFinished() ? !TOUCH_INTERCEPT : TOUCH_INTERCEPT;
	        break;
    	case MotionEvent.ACTION_MOVE:
	        /*
	         * Locally do absolute value. mLastMotionX is set to the y value of the down event.
	         */
	        final int xDiff = (int) Math.abs(x - mLastMotionX);
	        final int yDiff = (int) Math.abs(y - mLastMotionY);
	        boolean xMoved = xDiff > mTouchSlop;
	        boolean yMoved = yDiff > mTouchSlop;
	
	        if (xMoved || yMoved) {
	
	          if (xMoved && !yMoved) {
	            // Scroll if the user moved far enough along the X axis
	            mTouchState = TOUCH_INTERCEPT;
	          }else{
	        	  mTouchState = !TOUCH_INTERCEPT;
	          }
	        }
	        break;

      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        mTouchState = !TOUCH_INTERCEPT;
        break;
    }

    /*
     * The only time we want to intercept motion events is if we are in the drag mode.
     */
    return mTouchState == TOUCH_INTERCEPT;
  }

  /**
   * Track the touch event
   */
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

    final int action = ev.getAction();
    final float x = ev.getX();

    switch (action) {
      case MotionEvent.ACTION_DOWN:
        /*
         * If being flinged and user touches, stop the fling. isFinished will be false if being flinged.
         */
        if (!mScroller.isFinished()) {
          mScroller.abortAnimation();
        }
        // Remember where the motion event started
        mLastMotionX = x;
        break;
      case MotionEvent.ACTION_MOVE:
          // Scroll to follow the motion event
          final int deltaX = (int) (mLastMotionX - x);
          mLastMotionX = x;
          mCurrentScrollX = 0;
          if (deltaX < 0) {
            if (getScrollX() > 0) {
            	mCurrentScrollX = Math.max(-getScrollX(), deltaX);
//              scrollBy(Math.max(-getScrollX(), deltaX), 0);
//           	int index = getCurrentScreen();
//            	if(0 == index){
//            		View view = getChildAt(index);
//            		mSubViewRect = view.getL
//            		view.layout(l, t, r, b)
//            	}
            }else{
            	Log.v("getScrollX is < 0", String.valueOf(getScrollX()));
            	if(getScrollX() > -mScrollingBounce){
            		mCurrentScrollX = Math.min(deltaX, mScrollingBounce);
            	}
            	
            }
          }
          else if (deltaX > 0) {
            final int availableToScroll = 
            	getChildAt(getChildCount() - 1).getRight() - getScrollX() - getWidth() + mScrollingBounce;
            if (availableToScroll > 0) {
            	mCurrentScrollX = Math.min(availableToScroll, deltaX);
//              scrollBy(Math.min(availableToScroll, deltaX), 0);
            }
          }
          scrollBy(mCurrentScrollX, 0);
        break;
      case MotionEvent.ACTION_UP:
        //if (mTouchState == TOUCH_STATE_SCROLLING || mCurrentScreen == 1) {
          if(true){
          final VelocityTracker velocityTracker = mVelocityTracker;
          velocityTracker.computeCurrentVelocity(1000);
          int velocityX = (int) velocityTracker.getXVelocity();

          if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
            // Fling hard enough to move left
            scrollToScreen(mCurrentScreen - 1);
          }
          else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
            // Fling hard enough to move right
            scrollToScreen(mCurrentScreen + 1);
          }
          else {
            snapToDestination();
          }

          if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
          }
        }
        mTouchState = !TOUCH_INTERCEPT;
        break;
      case MotionEvent.ACTION_CANCEL:
        mTouchState = !TOUCH_INTERCEPT;
    }

    return true;
  }

  /**
   * 根据当前的位置判断出要滑动到哪一屏
   * 避免出现卡在两屏幕中间的情况!
   */
  private void snapToDestination() {
    final int screenWidth = getWidth();
    final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
    Log.d("workspace", "snapToDestination");
    scrollToScreen(whichScreen);
  }

  /**
   * Scroll to a specific screen
   *
   * @param whichScreen
   */
  private void scrollToScreen(int whichScreen) {
    Log.d("workspace", "snapToScreen=" + whichScreen);

    boolean changingScreens = whichScreen != mCurrentScreen;

    mNextScreen = whichScreen;

    View focusedChild = getFocusedChild();
    if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
      focusedChild.clearFocus();
    }
    
    final int newX = whichScreen * getWidth();
    final int delta = newX - getScrollX();
    Log.d("workspace", "newX=" + newX + " scrollX=" + getScrollX() + " delta=" + delta);
    mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 5);
    invalidate();

	if (mScrollToScreenCallback != null) {
		mScrollToScreenCallback.callback(whichScreen);
	}
  }

	public interface ScrollToScreenCallback {
		public void callback(int currentIndex);
	}
}